﻿using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Text;
using Nemerle.Utility;
using Nemerle.Extensions;
using PT = Nemerle.Compiler.Parsetree;

using System;
using SCG = System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Text.RegularExpressions;

namespace System.Web.Mvc
{
  macro ViewModelCtor(params args : array[expr])
  syntax("viewmodel", "(", args, ")")
  {
    ViewModelImpl.BuildViewModel(Macros.ImplicitCTX(), NList.ToList(args));
  }

  module ViewModelImpl
  {
    public BuildViewModel(typer : Typer, args : list[PT.PExpr]) : PT.PExpr
    {
      def controller = {
        def ty_name = typer.CurrentTypeBuilder.Name;
        regexp match(ty_name) {
          | @"(?<name>.*?)Controller$" => name
          | _ => ty_name
        }
      }

      def action = typer.CurrentMethodBuilder.Name;

      def args = NList.FoldRight(args, [], fun(arg, acc) {
        match(arg) {
          | <[ $(field : name) = $value ]> => (field, value) :: acc
          | <[ _.$(field : name) ]> => (field, arg) :: acc
          | <[ $(field : name) ]> => (field, arg) :: acc
          | _ => Message.Warning(arg.Location, "Expected model parameter initailizer in form: `x = foo()'"); acc
        }
      });

      def build_code(model_ty) {
          def ctor_args = args.Map( ((name, value)) => <[ $(name : name) = $value ]> );
          <[
            ViewModels.$(controller : dyn).$(model_ty.Name : dyn)( ..$ctor_args );
          ]>
      }

      def ns = ["ViewModels", controller];
      
      unless (typer.Manager.CoreEnv.OpenNamespaces.Any(t => t.FullName == ns))
        _ = typer.Manager.CoreEnv.AddOpenNamespace(ns, Location.Default);
      
      def env = typer.Manager.CoreEnv.EnterIntoNamespace(ns);
      
      match(env.LookupType([action])) {
        | Some(ty) => build_code(ty)
        | None =>
          def tb = env.Define(<[ decl:
            public class $(action : usesite) { }
          ]>);
          def compile_and_build() {
            unless(Message.ErrorCount > 0)
              tb.Compile();
            build_code(tb)
          }
          def infer_args_and_build(names, targs) {
            typer.DelayMacro(fun(fail_loudly) {
                
              def define_members(names_and_tys) {
                def prop_infos = names_and_tys.Map( ((name, ty)) => new(ty, field = Macros.NewSymbol(name.ToString()), prop = name) );
                { // ctor
                  def ctor_args = prop_infos.Map( info => <[ parameter: $(info.prop : name) : $(info.ty : typed) ]> );
                  def ctor_body = prop_infos.Map( info => <[ this.$(info.field : name) = $(info.prop : name) ]> );
                  tb.Define(<[ decl:
                    public this( ..$ctor_args ) { ..$ctor_body }
                  ]>);
                }
                { // fields and properties
                  foreach(info in prop_infos) {
                    tb.Define(<[ decl:
                      private $(info.field : name) : $(info.ty : typed)
                    ]>);
                    tb.Define(<[ decl:
                      public $(info.prop : name) : $(info.ty : typed) {
                        get { this.$(info.field : name) }
                      }
                    ]>);
                  }
                }
              }

              match(targs.Type.Hint) {
                | None => 
                  when(fail_loudly)
                    Message.Error("Compiler can't infer type of model arguments.");
                  None()

                | Some(FixedType.Tuple(tys)) =>
                  define_members(NList.Combine(names, tys.Map( _.Fix())));
                  Some(compile_and_build())

                | Some(ty) =>
                  define_members([(names.Head, ty)]);
                  Some(compile_and_build())
              }
            })
          }

          match(args) {
            | [] => compile_and_build()
            | _ =>
                def (names, values) = args.Split();
                infer_args_and_build(names, typer.TypeExpr(<[ (..$values) ]>))
          }
      }
    }
  }
}
